package com.wondersgroup.restbase.controller;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;

import com.wondersgroup.restbase.dto.DtoUtils;
import com.wondersgroup.restbase.dto.RestResult;
import com.wondersgroup.restbase.entity.CreateDateTime;
import com.wondersgroup.restbase.entity.TimeTrackingData;
import com.wondersgroup.restbase.service.BaseService;
import com.wondersgroup.restbase.util.AccessorUtils;
import com.wondersgroup.restbase.util.WebUtil;

/**
 * Web控制器的抽象基类
 * @author wondersgroup
 *
 * @param <DTO> 数据传输对象实体类泛型参数，用于收集前端传递的请求数据
 * @param <PK> 业务实体的主键类型泛型参数
 * @param <MODEL> 业务实体的类型泛型参数
 */
public abstract class BaseController<DTO, PK extends Serializable, MODEL> {
	Logger logger = Logger.getLogger(BaseController.class);

	public BaseController() {
	}

	/**
	 * 映射为/子类声明根路径/，一般用来返回所有业务实体数据，基于get请求
	 * @return 业务实体数据的集合，返回给前端的是json数组
	 */
	@RequestMapping(method = { RequestMethod.GET })
	@ResponseBody
	public List<DTO> index() {
		List<DTO> dtos = new ArrayList<DTO>();
		List<MODEL> modles = getService().loadAll();
		for (MODEL m : modles) {
			dtos.add(toDto(m));
		}
		return dtos;
	}

	/**
	 * 处理新增业务数据请求的方法，一般映射为/子类声明路径/，基于post方式
	 * @param map 前台传递的请求参数映射为map对象
	 * @return 返回新增业务数据转换的dto对象
	 * @throws Exception 处理中出现异常会抛出，由mvc框架注册的集中异常处理器进行处理
	 */
	@RequestMapping(method = { RequestMethod.POST })
	@ResponseBody
	public RestResult<DTO> create(@RequestBody Map<String, String> map) throws Exception {
		return getDtoRestResult(getService().save(newModel(mapToDto(map))));
	}

	/**
	 * 处理更新业务数据请求的方法，一般映射为/子类声明路径/id，基于put方式
	 * @param map 前台传递的请求参数映射为map对象
	 * @param id 路径参数，业务数据的主键
	 * @return 返回更新业务数据转换的dto对象
	 * @throws Exception 处理中出现异常会抛出，由mvc框架注册的集中异常处理器进行处理
	 */
	@RequestMapping(value = "/{id}", method = { RequestMethod.PUT })
	@ResponseBody
	public RestResult<DTO> update(@RequestBody Map<String, String> map, @PathVariable PK id) throws Exception {
		return getDtoRestResult(getService().save(update(mapToDto(map), getService().load(id))));
	}

	/**
	 * 将前端传递的请求map转换为dto的方法
	 * @param map 包含请求的map对象
	 * @return dto对象
	 * @throws Exception 处理中出现异常会抛出，由mvc框架注册的集中异常处理器进行处理
	 */
	protected DTO mapToDto(Map<String, String> map) throws Exception {
		DTO dto = getDTOClass().newInstance();
		Iterator<String> it = map.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			String val = String.valueOf(map.get(key));
			setValue(dto, key, val);
		}
		return dto;
	}

	/**
	 * 对dto的prop属性设置value值的方法，其中需要对value进行类型转换
	 * @param dto 数据传输对象
	 * @param prop dto的属性
	 * @param value 设置dto的属性值，字符串类型，需要转换为属性的类型
	 * @throws Exception 处理中出现异常会抛出，由mvc框架注册的集中异常处理器进行处理
	 */
	protected void setValue(DTO dto, String prop, String value) throws Exception {
		Method setter = AccessorUtils.findSetter(dto, prop);
		logger.debug("setValue: prop= " + prop + ", val=" + value);
		if (setter == null) {
			logger.error("prop=" + prop + "not found in dto!");
		} else {
			setter.invoke(dto, cast(prop, value, setter.getParameterTypes()[0]));

		}
	}

	/**
	 * 将map的value字符串转换为dto属性的方法，目前支持number类型的转换
	 * @param prop dto的属性名
	 * @param value map的value字符串
	 * @param clazz dto的属性Class对象
	 * @return 转换之后的dto的属性值
	 */
	protected <FT> FT cast(String prop, String value, Class<FT> clazz) {
		if(clazz.isPrimitive() && 
				clazz != Boolean.TYPE &&  
				clazz != Character.TYPE &&
				clazz != Void.TYPE) {
			return str2num(value, clazz);
		}
		else if(Number.class.isAssignableFrom(clazz)) {
			return str2numObj(value, clazz);
		}
		return clazz.cast(value);
	}

	/**
	 * 将字符串转换为基本数值类型的方法
	 * @param value 字符串值
	 * @param clazz 基本数值类型的Class对象
	 * @return 基本数值
	 */
	@SuppressWarnings("unchecked")
	private <FT> FT str2num(String value, Class<FT> clazz) {
		try {
			Number num = NumberFormat.getInstance().parse(value);
			Method method = num.getClass().getDeclaredMethod(clazz.getName() + "Value");
			return (FT) method.invoke(num);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 将字符串转换为引用数值类型的方法
	 * @param value 字符串值
	 * @param clazz 引用数值类型的Class对象
	 * @return 引用数值对象
	 */
	@SuppressWarnings("unchecked")
	private <FT> FT str2numObj(String value, Class<FT> clazz) {
		try {
			Method method = clazz.getDeclaredMethod("valueOf", String.class);
			return (FT) method.invoke(null, value);
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
	}

	/**
	 * 处理删除业务数据请求的方法，一般映射为/子类声明路径/id，基于delete方式
	 * @param id 业务数据的主键
	 * @return 已删除的业务数据
	 */
	@RequestMapping(value = "/{id}", method = { RequestMethod.DELETE })
	@ResponseBody
	public RestResult<DTO> delete(@PathVariable PK id) {
		MODEL model = getService().load(id);
		getService().delete(id);
		return getDtoRestResult(model);
	}

	/**
	 * 处理业务数据加载请求的方法，一般映射为/子类声明路径/id，基于get方式
	 * @param id 业务数据的主键
	 * @return 加载的业务数据
	 */
	@RequestMapping(value = "/{id}", method = { RequestMethod.GET })
	@ResponseBody
	public DTO load(@PathVariable PK id) {
		return toDto(getService().load(id));
	}

	/**
	 * 将业务数据包装的类型，最终会转换为前端接收到的json格式
	 * @param model 业务实体
	 * @return Rest包裹对象
	 */
	protected RestResult<DTO> getDtoRestResult(MODEL model) {
		RestResult<DTO> dtoRestResult = new RestResult<DTO>(toDto(model));
		return dtoRestResult;
	}

	/**
	 * 获得子类映射基路径的方法
	 * @return 子类映射基路径
	 */
	protected String getBasePath() {
		return this.getClass().getAnnotation(RequestMapping.class).value()[0];
	}

	/**
	 * 将业务数据转换为dto对象的方法
	 * @param model 业务实体
	 * @return dto对象
	 */
	protected DTO toDto(MODEL model) {
		if (model == null)
			return null;
		try {
			return DtoUtils.toDto(model, getDTOClass());
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/**
	 * 将业务实体集合转换为dto对象集合的方法
	 * @param models 业务实体集合
	 * @return dto对象集合
	 */
	protected List<DTO> toDto(List<MODEL> models) {
		if (models == null)
			return null;
		try {
			return DtoUtils.toDto(models, getDTOClass());
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/**
	 * 通过dto创建新业务实体的方法
	 * @param dto 数据传输对象
	 * @return 业务实体对象
	 */
	protected MODEL newModel(DTO dto) {
		if (dto == null)
			return null;
		try {
			MODEL model = DtoUtils.newModel(dto, getModelClass());
			newTrack(model);
			return model;
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/**
	 * 为业务实体附加创建时间和更新时间的方法
	 * @param model 业务实体
	 * @throws IllegalAccessException 反射的set方法无权访问时会抛出该异常
	 */
	private void newTrack(MODEL model) throws IllegalAccessException {
		Field trackField = getTrackField(model);
		if (trackField != null) {
			String name = trackField.getName();
			if (name.equals("createDateTime")) {
				trackField.setAccessible(true);
				trackField.set(model, new CreateDateTime(new Date()));
			} else {
				trackField.setAccessible(true);
				trackField.set(model, new TimeTrackingData(new Date(), new Date()));
			}
		}
	}

	/**
	 * 获取创建时间或更新时间字段对象的方法
	 * @param model 业务实体
	 * @return 时间字段对象
	 */
	private Field getTrackField(MODEL model) {
		Field trackField = null;
		for (Field field : getModelClass().getDeclaredFields()) {
			// System.out.println("field: " + field.getName());
			if (field.getType().equals(TimeTrackingData.class) || field.getType().equals(CreateDateTime.class)) {
				trackField = field;
				break;
			}

		}
		return trackField;
	}

	/**
	 * 通过dto更新业务实体的方法
	 * @param dto 数据传输对象
	 * @param toUpdate 更新前的业务实体
	 * @return 跟新后的业务实体
	 */
	protected MODEL update(DTO dto, MODEL toUpdate) {
		if (dto == null || toUpdate == null)
			return null;
		try {
			MODEL model = DtoUtils.update(dto, toUpdate);
			updateTrack(model);
			return model;
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/**
	 * 设置业务实体更新时间的方法
	 * @param model 业务实体
	 * @throws IllegalAccessException 反射的set方法无权访问时会抛出该异常
	 */
	private void updateTrack(MODEL model) throws IllegalAccessException {
		Field trackField = getTrackField(model);
		if (trackField != null) {
			trackField.setAccessible(true);
			TimeTrackingData data = (TimeTrackingData) trackField.get(model);
			data.setUpdatedAt(new Date());
		}
	}
	
	protected HttpServletRequest getRequest() {
		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
	}

	protected HttpServletResponse getResponse() {
		return ((ServletWebRequest) RequestContextHolder.getRequestAttributes()).getResponse();
	}
	
	protected HttpSession getSession() {
		return getRequest().getSession();
	}
	
	protected HttpSession getSession(boolean create) {
		return getRequest().getSession(create);
	}
	
	protected ServletContext getServletContext() {
		return getRequest().getSession().getServletContext();
	}

	protected String getSessionUserInfo(HttpSession session, String propertyName) {
		return WebUtil.getSessionUserInfo(session, propertyName);
	}

	protected Map<String, String> getCreateUserMap(HttpSession session) throws Exception {
		return WebUtil.getCreateUserMap(session);
	}

	protected Map<String, String> getUpdateUserMap(HttpSession session) throws Exception {
		return WebUtil.getUpdateUserMap(session);
	}

	protected Map<String, String> getCreateUserAndCreateOrgMap(HttpSession session) throws Exception {
		return WebUtil.getCreateUserAndCreateOrgMap(session);
	}

	protected Map<String, Object> jsonToObject(String jsonStr) {
		return WebUtil.jsonToObject(jsonStr);
	}

	protected List<String> getFileList(Map<String, Object> map, List<String> strList) {
		return WebUtil.getFileList(map, strList);
	}

	/**
	 * 子类controller为父类提供业务相关服务对象的方法
	 * @return
	 */
	abstract protected BaseService<MODEL> getService();

	/**
	 * 子类controller为父类指明数据传输对象类型的方法
	 * @return dto的类型对象
	 */
	abstract protected Class<DTO> getDTOClass();

	/**
	 * 子类controller为父类指明实体类型的方法
	 * @return 业务实体的类型对象
	 */
	abstract protected Class<MODEL> getModelClass();

}
